/*
	ViewSelectShip.cpp
	(c)2000 Palestar, Richard Lyle
*/

#define INTERFACE_DLL
#include "Debug/Assert.h"
#include "Display/PrimitiveSetTransform.h"
#include "Display/PrimitiveLineList.h"
#include "Display/PrimitiveMaterial.h"
#include "Gui3d/WindowLayout.h"
#include "Interface/GameDocument.h"
#include "Game/GadgetWeapon.h"
#include "Game/GadgetBeamWeapon.h"
#include "Game/CargoGadget.h"
#include "Game/NounUnit.h"
#include "Game/NounJumpGate.h"
#include "Interface/WindowObserve.h"
#include "Interface/ViewSelectShip.h"

#include <stdio.h>



//----------------------------------------------------------------------------

static Constant UPDATE_SHIP_LIST_TIME( "UPDATE_SHIP_LIST_TIME", 1.0f );

//----------------------------------------------------------------------------

IMPLEMENT_FACTORY( ViewSelectShip, WindowView::View );
REGISTER_FACTORY_KEY( ViewSelectShip, 4096637078369204814 );

ViewSelectShip::ViewSelectShip() : m_bShipSelected( false )
{
	m_FilterType = NounShip::UNKNOWN;
}

//----------------------------------------------------------------------------

void ViewSelectShip::onActivate()
{
	//{{BEGIN_DATA_INIT
	DATA_MAP( m_pTextLabel, WindowText, "Label" );
	DATA_MAP( m_pNavigationWindow, WindowNavigation, "NavigationWindow" );
	DATA_MAP( m_pTextShips, WindowText, "TextShips" );
	DATA_MAP( m_pTextCargo, WindowText, "TextCargo" );
	m_pButtonRemoveCargo = WidgetCast<WindowButton>( window()->findNode( "ButtonRemoveCargo" ) );
	m_pCargoButtons = WidgetCast<NodeWindow>( window()->findNode( "CargoButtons" ) );
	m_pButtonRemoveShip = WidgetCast<WindowButton>( window()->findNode( "ButtonRemoveShip" ) );
	m_pSlotButtons = WidgetCast<NodeWindow>( window()->findNode( "SlotButtons" ) );
	m_pLeftConsole = WidgetCast<NodeWindow>( window()->findNode( "LeftConsole" ) );
	m_pButtonUnknown = WidgetCast<WindowButton>( window()->findNode( "ButtonUnknown" ) );
	m_pButtonStation = WidgetCast<WindowButton>( window()->findNode( "ButtonStation" ) );
	m_pButtonSupply = WidgetCast<WindowButton>( window()->findNode( "ButtonSupply" ) );
	m_pButtonEngineer = WidgetCast<WindowButton>( window()->findNode( "ButtonEngineer" ) );
	m_pButtonTransport = WidgetCast<WindowButton>( window()->findNode( "ButtonTransport" ) );
	m_pButtonDread = WidgetCast<WindowButton>( window()->findNode( "ButtonDread" ) );
	m_pButtonCruiser = WidgetCast<WindowButton>( window()->findNode( "ButtonCruiser" ) );
	m_pButtonDestroyer = WidgetCast<WindowButton>( window()->findNode( "ButtonDestroyer" ) );
	m_pButtonFrigate = WidgetCast<WindowButton>( window()->findNode( "ButtonFrigate" ) );
	m_pButtonScout = WidgetCast<WindowButton>( window()->findNode( "ButtonScout" ) );
	m_pShipButtons = WidgetCast<NodeWindow>( window()->findNode( "ShipButtons" ) );
	m_pIncomingMessages = WidgetCast<NodeWindow>( window()->findNode( "IncomingMessages" ) );
	m_pChatEdit = WidgetCast<WindowEdit>( window()->findNode( "ChatEdit" ) );
	m_pChatEditWindow = WidgetCast<NodeWindow>( window()->findNode( "ChatEditWindow" ) );
	m_pShipDescriptionFrame = WidgetCast<NodeWindow>( window()->findNode( "ShipDescriptionFrame" ) );
	m_pShipRender = WidgetCast<NodeWindow>( window()->findNode( "ShipRender" ) );
	m_pButtonOkay = WidgetCast<WindowButton>( window()->findNode( "ButtonOkay" ) );
	m_pShipDescription = WidgetCast<WindowText>( window()->findNode( "ShipDescription" ) );
	//END_DATA_INIT}}

	GameDocument * pDoc = (GameDocument *)document();
	ASSERT( pDoc );

	m_pLastSpawn = pDoc->lastSpawn();
	// make sure the teamId is valid
	if (! pDoc->isTeamValid() )
		pDoc->setScene( "SelectTeam" );		// team selection lost or invalid

	// select a spawn point if none is already selected, to make it more dummy proof!
	if (! m_pLastSpawn.valid() || m_pLastSpawn->context() != pDoc->context() )
	{
		GameContext * pContext = pDoc->context();
		ASSERT( pContext );

		Array< Noun * > spawns;
		for(int i=0;i<pContext->zoneCount();i++)
		{
			NodeZone * pZone = pContext->zone( i );
			for(int j=0;j<pZone->childCount();j++)
			{
				Noun * pNoun = WidgetCast<Noun>( pZone->child(j) );
				if (! pNoun )
					continue;

				if ( pDoc->validateSpawn( pNoun ) )
					spawns.push( pNoun );
			}
		}

		if ( spawns.size() > 0 )
			m_pLastSpawn = spawns[ rand() % spawns.size() ];
	}

	// request profile update
	pDoc->client()->sendProfile();
	// make our spawn the current target
	pDoc->setTarget( m_pLastSpawn );
	// put navigation window into spawn mode
	m_pNavigationWindow->onActivate();
	m_pNavigationWindow->setSelectMode( WindowNavigation::SELECT_SPAWN );
	m_pNavigationWindow->setShowFlags( WindowNavigation::SHOW_SPAWN|WindowNavigation::SHOW_ENEMY|
		WindowNavigation::SHOW_LINKS|WindowNavigation::SHOW_PLANETS|WindowNavigation::SHOW_SHIPS|
		WindowNavigation::SHOW_ORBITS|WindowNavigation::SHOW_GRID );

	m_pNavigationWindow->onCenter();	// center the map on our current spawn position
	m_pNavigationWindow->onFull();

	m_pSelectedShip = NULL;
	m_SelectedCargo.release();

	m_ShipRadius = 5.0f;
	m_ShipZoom = 5.0f;
	m_UpdateShipList = UPDATE_SHIP_LIST_TIME;

	m_pCargoButtons->detachAllNodes();
	m_pSlotButtons->detachAllNodes();
	m_pShipButtons->detachAllNodes();

	// create the spin node
	m_ShipSpin = new NodeSpin;
	m_ShipSpin->setSpin( 0, 0, 0 );

	m_pShipDescription->setText( "" );
	m_pButtonOkay->setEnable( false );
	m_pChatEditWindow->hideWindow();

	// reset the camera time
	GameDocument::s_fCamera = 0.0f;
	// call the script
	pDoc->context()->scriptCall( "onShipSelect()" );
	// update the ship list on the first call to onUpdate()
	m_UpdateShipList = -1.0f;
}

void ViewSelectShip::onDeactivate()
{
	// called before this view is destroyed
	m_ShipSpin = NULL;

	m_pCargoButtons->detachAllNodes();
	m_pSlotButtons->detachAllNodes();
	m_pShipButtons->detachAllNodes();
}

void ViewSelectShip::onUpdate( float t )
{
	ASSERT( m_pShipButtons );

	GameDocument * pDoc = (GameDocument *)document();
	ASSERT( pDoc );
	GameClient * pClient = pDoc->client();
	ASSERT( pClient );
	GameContext * pContext = WidgetCast<GameContext>( pClient->context() );
	ASSERT( pContext );

	// validate the spawn point, it may be lost while we are selecting a ship so check it every update!
	if (! pDoc->isTeamValid() )
		pDoc->setScene( "SelectTeam" );		// team selection lost or invalid
	else if ( pDoc->isShipValid() )
		pDoc->setScene( "Game" );			// ship is already valid, move into the game

	// enable / disable the ok button...
	m_pButtonOkay->setEnable( m_bShipSelected && !pClient->inQueue() );

	// update the queue status..
	if ( pClient->inQueue() )
	{
		m_pTextLabel->setText( CharString().format("SERVER FULL, WAIT QUEUE %d of %d..", 
			pClient->queuePosition(), pClient->queueSize() ) );
	}
	else
		m_pTextLabel->setText( "" );

	// update the alpha of the filter buttons
	for(int i=0;i<NounShip::TYPE_COUNT;i++)
	{
		WindowButton * pButton = getFilterButton( (NounShip::Type)i );
		pButton->setIconColor( (m_FilterType == i || m_FilterType == NounShip::UNKNOWN) ? WHITE : GREY );
	}

	// implement view updating
	m_ShipZoom += (m_ShipRadius - m_ShipZoom) * Clamp( t, 0.0f, 1.0f / 15.0f );
	// show/hide chat edit window
	m_pChatEditWindow->setVisible( m_pChatEdit->editing() );

	// update the ship and cargo buttons
	m_UpdateShipList -= t;
	if ( m_UpdateShipList < 0.0f && pDoc->isTeamValid() )
	{
		m_UpdateShipList = UPDATE_SHIP_LIST_TIME;
		updateShipList();
	}
}

bool ViewSelectShip::onMessage( const Message & msg )
{
	//{{BEGIN_MSG_MAP
	MESSAGE_MAP( WB_BUTTONUP, 36851163, onButtonRemoveCargo);
	MESSAGE_MAP( WB_BUTTONUP, 3642064065, onButtonRemoveShip);
	MESSAGE_MAP( WB_BUTTONUP, 1772059130, onButtonUnknown);
	MESSAGE_MAP( WB_BUTTONUP, 4231797196, onButtonStation);
	MESSAGE_MAP( WB_BUTTONUP, 1693709888, onButtonSupply);
	MESSAGE_MAP( WB_BUTTONUP, 1039534350, onButtonEngineer);
	MESSAGE_MAP( WB_BUTTONUP, 1167464310, onButtonTransport);
	MESSAGE_MAP( WB_BUTTONUP, 761092843, onButtonDread);
	MESSAGE_MAP( WB_BUTTONUP, 1440821965, onButtonCruiser);
	MESSAGE_MAP( WB_BUTTONUP, 3329186848, onButtonDestroyer);
	MESSAGE_MAP( WB_BUTTONUP, 1829270533, onButtonFrigate);
	MESSAGE_MAP( WB_BUTTONUP, 38275307, onButtonScout);
	MESSAGE_MAP( WE_EDITDONE, 3966988240, onChatEdit);
	MESSAGE_MAP( WB_BUTTONUP, 1293570276, onCancel);
	MESSAGE_MAP( WB_BUTTONUP, 1507228051, onOkay);
	//END_MSG_MAP}}

	switch( msg.message )
	{
	case HM_CHAR:
		switch( msg.wparam )
		{
		case HK_RETURN:
			m_pChatEditWindow->showWindow();
			m_pChatEdit->onBeginEdit();
			return true;
		}
		break;
	}

	return false;
}

void ViewSelectShip::onDocumentUpdate()
{
	// document data has changed, update this view if needed
}

const float CAMERA_PITCH = PI / 4;
const float CAMERA_YAW_RATE = PI / 32;

void ViewSelectShip::onRender( RenderContext & context, const RectInt & window )
{
	// render this view, window is in screen space
	GameDocument * pDoc = (GameDocument *)document();
	if ( pDoc == NULL )
		return;
	if (! m_ShipSpin.valid() )
		return;

	// get the display device
	DisplayDevice * pDisplay = context.device();
	ASSERT( pDisplay );

	// render the selected ship
	RectInt previousWindow = context.window();

	// calculate the camera frame and position
	Matrix33 cameraFrame( true );
	cameraFrame.rotate( CAMERA_PITCH, CAMERA_YAW_RATE * context.time(), 0 );
	Vector3 cameraPosition( m_ShipSpin->position() - (cameraFrame.k * m_ShipZoom) );

	context.setPosition( cameraPosition );
	context.setFrame( cameraFrame );

	// set the window to render the ship inside
	RectInt renderWindow( m_pShipRender->window() );
	renderWindow.inset( m_pShipRender->windowStyle()->borderSize() );
	context.setWindow( renderWindow );
	context.flush();

	pDisplay->setAmbient( WHITE );
	pDisplay->clearLights();

	// don't render gadget models
	NounGadget::s_bRenderGadgets = false;
	// render the ship 
	context.render( m_ShipSpin );
	// restore gadget rendering
	NounGadget::s_bRenderGadgets = true;

	// render a grid below the ship
	Vector3 n( 0, 1, 0 );

	const int		GRID_STEPS = 15;
	const float		GRID_SIZE = 30.0f;
	const float		GRID_CENTER = GRID_SIZE / 2.0f;
	const float		GRID_STEP = GRID_SIZE / GRID_STEPS;
	const int		GRID_LINES = GRID_STEPS + GRID_STEPS + 2;

	Line gridLines[ GRID_LINES ];
	for(int x=0;x<=GRID_STEPS;x++)
	{
		float dx = (GRID_STEP * x) - GRID_CENTER;

		gridLines[ x ] = Line( 
			Vertex( Vector3( dx, 0, -GRID_CENTER ), n, 0, 0 ),
			Vertex( Vector3( dx, 0, GRID_CENTER ), n, 0, 0 ) );
	}
	for(int z=0;z<=GRID_STEPS;z++)
	{
		float dz = (GRID_STEP * z) - GRID_CENTER;

		gridLines[ GRID_STEPS + 1 + z ] = Line( 
			Vertex( Vector3( -GRID_CENTER, 0, dz), n, 0, 0 ),
			Vertex( Vector3( GRID_CENTER, 0, dz), n, 0, 0 ) );
	}

	// push a material of the proper color
	Material::push( context, Color(0,224,40,255), true, PrimitiveMaterial::ADDITIVE );
	PrimitiveSetTransform::push( pDisplay, ~cameraFrame, cameraFrame * ((Vector3( 0,-2,0 ) - cameraPosition)) );
	PrimitiveLineList::push( pDisplay, GRID_LINES, gridLines );

	// restore the previous window
	context.setWindow( previousWindow );
	context.flush();
	
	// restore the ambient light
	pDisplay->setAmbient( UNIVERSE_AMBIENT );
}

//----------------------------------------------------------------------------

bool ViewSelectShip::onOkay(const Message & msg)
{
	GameDocument * pDoc = (GameDocument *)document();

	Array< WidgetKey > cargo;
	for(int i=0;i<m_SelectedCargo.size();i++)
		cargo.push( m_SelectedCargo[i]->key() );

	pDoc->client()->selectSelf( pDoc->target(), m_pSelectedShip, cargo );

	// callback into the script
	CharString sSelf = m_pSelectedShip.key().string();
	pDoc->context()->scriptCall( CharString().format("onShipSelected( %s )", sSelf) );

	// set the last selected spawn
	m_pLastSpawn = pDoc->target();
	pDoc->setLastSpawn( m_pLastSpawn );

	return true;
}

bool ViewSelectShip::onCancel(const Message & msg)
{
	document()->setScene( "Main" );
	return true;
}

bool ViewSelectShip::onChatEdit(const Message & msg)
{
	GameDocument * pDoc = (GameDocument *)document();
	ASSERT( pDoc );

	CharString send( m_pChatEdit->text() );
	if ( send.length() > 0 )
	{
		pDoc->sendChat( send );
		m_pChatEdit->setText( "" );
	}
	return true;
}

bool ViewSelectShip::onButtonRemoveShip(const Message & msg)
{
	GameDocument * pDoc = (GameDocument *)document();
	ASSERT( pDoc );

	if ( m_pSelectedShip.valid() )
	{
		Array< WidgetKey > remove;
		remove.push( m_pSelectedShip->key() );

		pDoc->client()->removeStorage( remove );
		m_UpdateShipList = 0.0f;

		onSelectShip( NULL, NULL );
	}
	return true;
}

bool ViewSelectShip::onButtonRemoveCargo(const Message & msg)
{
	GameDocument * pDoc = (GameDocument *)document();
	ASSERT( pDoc );

	Array< WidgetKey > remove;
	for(int i=0;i<m_SelectedCargo.size();i++)
		remove.push( m_SelectedCargo[i]->key() );

	pDoc->client()->removeStorage( remove );
	m_UpdateShipList = 0.0f;

	m_SelectedCargo.release();
	return true;
}

bool ViewSelectShip::onButtonScout(const Message & msg)
{
	m_FilterType = NounShip::SCOUT;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonFrigate(const Message & msg)
{
	m_FilterType = NounShip::FRIGATE;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonDestroyer(const Message & msg)
{
	m_FilterType = NounShip::DESTROYER;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonCruiser(const Message & msg)
{
	m_FilterType = NounShip::CRUISER;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonDread(const Message & msg)
{
	m_FilterType = NounShip::DREAD;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonTransport(const Message & msg)
{
	m_FilterType = NounShip::TRANSPORT;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonEngineer(const Message & msg)
{
	m_FilterType = NounShip::ENGINEER;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonSupply(const Message & msg)
{
	m_FilterType = NounShip::SUPPLY;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonStation(const Message & msg)
{
	m_FilterType = NounShip::STATION;
	m_UpdateShipList = 0;
	return true;
}

bool ViewSelectShip::onButtonUnknown(const Message & msg)
{
	m_FilterType = NounShip::UNKNOWN;
	m_UpdateShipList = 0;
	return true;
}

//----------------------------------------------------------------------------

void ViewSelectShip::onSelectShip( NounShip * pShip, Noun * pSpawn )
{
	GameDocument * pDoc = (GameDocument *)document();
	ASSERT( pDoc );
	GameClient * pClient = pDoc->client();
	ASSERT( pClient );
	GameContext * pContext = pDoc->context();
	ASSERT( pContext );

	// by default disable the ok button
	m_bShipSelected = false;
	m_pButtonOkay->setEnable( false );

	// are we deselecting a ship
	if ( pShip == NULL )
	{
		m_pShipDescription->setText( "" );

		m_ShipSpin->detachAllNodes();
		m_pSelectedShip = NULL;
		return;
	}

	if ( pDoc->validateSpawn( pSpawn ) )
	{
		// set the spawn as the target
		pDoc->setTarget( pSpawn );
		// center the navigation window
		m_pNavigationWindow->onCenter();
	}

	// find the shipcontext for the selected ship, if not found then it's from the players stash
	NounTemplate * pShipContext = NULL;
	if ( m_ShipContextHash.find( pShip->key() ).valid() )
		pShipContext = m_ShipContextHash[ pShip->key() ];

	m_bShipSelected = true;
	m_pButtonOkay->setEnable( m_bShipSelected && !pClient->inQueue() );

	ASSERT( m_ShipSpin.valid() );
	m_ShipSpin->detachAllNodes();
	m_pSelectedShip = pShip;

	// create a copy of the ship to show the player
	NounShip * pCopy = (NounShip *)pShip->copy();
	pCopy->setPosition( Vector3( true ) );
	pCopy->setSyncronized( true );		// so it will render

	m_ShipSpin->setPosition( Vector3( 0,0,0 ) );
	m_ShipSpin->attachNode( pCopy );
	m_ShipRadius = pShip->radius() * 3.0f;

	// display ship systems
	CharString primary, weapons;
	for(int i=0;i<pShip->childCount();i++)
		if ( WidgetCast<NounGadget>( pShip->child(i) ) )
		{
			NounGadget * pGadget = (NounGadget *)pShip->child(i);
			if ( pGadget->type() >= NounGadget::FIRST_WEAPON && pGadget->type() <= NounGadget::LAST_WEAPON )
				weapons += CharString().format("<X;20>%s\n", pGadget->nounContext()->name() );
			else
				primary += CharString().format("<X;20>%s\n", pGadget->nounContext()->name() );
		}

	CharString shipSystems;
	shipSystems += CharString().format("PRIMARY SYSTEMS:\n%s", primary );
	shipSystems += CharString().format("WEAPON SYSTEMS:\n%s", weapons);

	// show the requirements only if this is a new ship..
	CharString sRequire;

	GameClient::Storage * pStorage = pClient->storage( pShip->key() );
	if ( pStorage != NULL )
	{
		int nShipFactionId = pStorage->nFactionId;
		int nMyFactionId = pDoc->factionId();

		// check the faction of the ship and the player, let them know if they can't spawn it as their current faction or not..
		if ( nMyFactionId != nShipFactionId )
			sRequire += "<color;ff0000ff>FACTION INVALID!</color>\n";

		// show hull damage if any...
		if ( pShip->damage() > 0 )
		{
			int nDamage = (int)(pShip->damageRatioInv() * 100);
			if ( nDamage < 5 )
				nDamage = 5;		// don't show anything below 5%, because we'll respawn it will atleast 5% hull
			sRequire += CharString().format( "<color;ff0000ff>HULL: %d%%</color>\n", nDamage );
		}

		Noun * pLocation = pStorage->pLocation;
		if ( pDoc->validateSpawn( pLocation ) )
		{
			bool bCanSpawn = pDoc->target() == pLocation && pDoc->validateSpawn( pLocation );
			sRequire += CharString().format("<color;%s>Docked: %s</color>\n", 
				bCanSpawn ? "ffffffff" : "ff0000ff", pLocation->name());
		}
	}
	else if ( pShipContext != NULL )
	{
		// display required rank
		int rank = pDoc->rank();
		if(rank < 0)
			rank =0;
		bool hasRank = rank >= pShipContext->rank();

		sRequire += CharString().format( "<color;%s>Rank: %s</color>\n", hasRank ? "ffffffff" : "ff0000ff", 
			GameProfile::rankText( pShipContext->rank() ) );

		// display required badges
		dword required = pShipContext->flags();
		dword badges = pDoc->badges();

		bool hasBadges = (required & badges) == required;

		sRequire += CharString().format( "<color;%s>Badges: ", hasBadges ? "ffffffff" : "ff0000ff" );
		if ( pShipContext->flags() != 0 )
		{
			for(int i=FIRST_BADGE;i<=LAST_BADGE;i++)
				if ( ((1 << i) & required) != 0 )
					sRequire += CharString().format("%s ", GameProfile::badgeText( i ) );

			sRequire += "</color>\n";
		}
		else
			sRequire += "</color>None\n";

		// spawn point may not be a planet
		NounPlanet * pPlanet = WidgetCast<NounPlanet>( pDoc->target() );
		if ( pPlanet != NULL )
		{
			// display build costs
			sRequire += "<color;ffffffff>Requirements:</color>\n";

			int nBuildCost = NounShip::buildCost( pShip );
			sRequire += CharString().format("<X;20;color;%s>%s Resources\n", 
				nBuildCost <= pPlanet->resources() ? "ffffff" : "ff0000ff",
				FormatNumber<char,int>( nBuildCost ) );

			int nTechnology = NounShip::buildTechnology( pShip );
			if ( nTechnology > 0 )
				sRequire += CharString().format("<X;20;color;%s>%d Technology\n", 
					pPlanet->technology() < nTechnology ? "ff0000ff" : "ffffffff", nTechnology );
			
			dword nFlags = NounShip::buildFlags( pShip );
			if ( nFlags != 0 )
			{
				for(int i=0;i<32;i++)
				{
					dword nBit = 1 << i;
					if ( (nFlags & nBit) != 0 )
					{
						sRequire += CharString().format("<X;20;color;%s>%s\n", 
							(pPlanet->flags() & nBit) ? "ffffffff" : "ff0000ff", NounPlanet::flagName( nBit ) );
					}
				}
			}

		}
		else if (! pShip->enableGateSpawn() )
			sRequire += "\n<color;ff0000ff>SHIPYARD REQUIRED!</color>\n";
	}
	
	CharString info;
	info += CharString().format("<b;l>%s</b;/l>\n\n", pShip->nounContext()->name() );
	info += CharString().format("%s\n", sRequire );
	info += "<color;ffffffff>";
	info += CharString().format("%s\n\n", pShip->nounContext()->description() );
	info += CharString().format("%s</color>\n", shipSystems );

	// show any enhancements that this ship has
	if(pShip->enhancementCount() > 0)
	{
		CharString enhancementInfo = CharString().format("<large><color;ffffff>Enhancements (%d / %d) </color></large>\n---------------\n",pShip->enhancementCount(),pShip->maxEnhancements());
		for(int i=0;i<pShip->childCount();i++)
		{
			NounEnhancement *pEnh = WidgetCast<NounEnhancement>(pShip->child(i));
			if(pEnh)
			{
				enhancementInfo += pEnh->GetDescription();
			}
		}
		info += enhancementInfo;
	}
	// set the ship description
	m_pShipDescription->setText( info );
}

void ViewSelectShip::onSelectCargo( NounCargo * pCargo )
{
	if ( m_SelectedCargo.search( pCargo ) < 0 )
		m_SelectedCargo.push( pCargo );
	else
		m_SelectedCargo.removeSearch( pCargo );
}


WindowButton * ViewSelectShip::getFilterButton( NounShip::Type type )
{
	switch( type )
	{
	case NounShip::SCOUT:
		return m_pButtonScout;
	case NounShip::FRIGATE:
		return m_pButtonFrigate;
	case NounShip::DESTROYER:
		return m_pButtonDestroyer;
	case NounShip::CRUISER:
		return m_pButtonCruiser;
	case NounShip::DREAD:
		return m_pButtonDread;
	case NounShip::ENGINEER:
		return m_pButtonEngineer;
	case NounShip::SUPPLY:
		return m_pButtonSupply;
	case NounShip::TRANSPORT:
		return m_pButtonTransport;
	case NounShip::STATION:
		return m_pButtonStation;
	}

	return m_pButtonUnknown;
}

bool ViewSelectShip::updateShipList()
{
	GameDocument * pDoc = (GameDocument *)document();
	ASSERT( pDoc );
	GameClient * pClient = pDoc->client();
	ASSERT( pClient );
	GameContext * pContext = pClient->gameContext();
	ASSERT( pContext );

	m_ShipContextHash.release();

	// get the spawn target
	Noun * pSpawn = pDoc->target();
	// is the spawn valid
	bool bSpawnValid = pDoc->validateSpawn( pSpawn );

	// get the player badges
	dword badges = pDoc->badges();
	// get the player rank
	int rank = pDoc->rank();
	if(rank < 0)
		rank = 0;
	// check the player flags
	bool isAdmin = pDoc->isAdmin();

	// hide all filter buttons, show the buttons as we enumerate the ships
	for(int i=0;i<NounShip::TYPE_COUNT;i++)
		getFilterButton( (NounShip::Type)i )->hideWindow();
	// always show the unknown filter button
	getFilterButton( NounShip::UNKNOWN )->showWindow();

	// count the number of ships added
	int shipCount = 0;

	// get the collection object containing the ships from the fleet
	Collection::Link pTemplates = pContext->team( pDoc->teamId() ).pTemplates;
	if ( pTemplates.valid() )
	{
		// enumerate all the avialble ship types
		for(i=0;i<pTemplates->resourceCount();i++)
		{
			// get the ship context
			NounTemplate::Ref pShipContext = WidgetCast<NounTemplate>( pTemplates->resource( i ) );
			if ( pShipContext.valid() )
			{
				NounShip * pShip = WidgetCast<NounShip>( pShipContext->noun() );
				if (! pShip )
					continue;
				// show the filter button for this ship type
				getFilterButton( pShip->type() )->showWindow();
				// apply the current filter if any
				if ( m_FilterType != NounShip::UNKNOWN && pShip->type() != m_FilterType )
					continue;		// filtered!
				if ( pShip->type() == NounShip::UNKNOWN && !isAdmin )
					continue;		// allow admins to select UNKNOWN ships only

				bool bCanBuild = false;
				if ( WidgetCast<NounPlanet>( pSpawn ) )
				{
					NounPlanet * pPlanet = (NounPlanet *)pSpawn;

					bCanBuild = NounShip::buildTechnology( pShip ) <= pPlanet->technology() && 
						NounShip::buildCost( pShip ) <= pPlanet->resources();
				}
				else if ( WidgetCast<NounJumpGate>( pSpawn ) )
				{
					bCanBuild = pShip->enableGateSpawn();
				}

				bool hasBadges = (pShipContext->flags() & badges) == pShipContext->flags();
				bool hasRank = rank >= pShipContext->rank();
				bool bAvailable = ((hasBadges && hasRank) || isAdmin) && bSpawnValid;

				// check for existing button, if found then update that button ... otherwise create a new button
				ShipButton * pButton = shipCount < m_pShipButtons->childCount() ? (ShipButton *)m_pShipButtons->child(shipCount) : NULL;
				if ( pButton != NULL )
					pButton->setButton( this, pShip, NULL, bAvailable, bCanBuild );						// update existing button
				else
					new ShipButton( m_pShipButtons, this, pShip, NULL, bAvailable, bCanBuild );			// create new button

				// hash the context
				m_ShipContextHash[ pShip->key() ] = pShipContext;

				shipCount++;
			}
		}
	}

	// remove extra buttons
	for(i=shipCount;i<m_pShipButtons->childCount();i++)
		((ShipButton *)m_pShipButtons->child(i))->destroy();
	
	int slotCount = 0;
	int cargoCount = 0;

	// display players storage
	for(i=0;i<pClient->storageCount();i++)
	{
		const GameClient::Storage & storage = pClient->storage( i );

		Noun::wRef pInstance( pClient->storageKey( i ) );
		if (! pInstance.valid() )
			pInstance = pClient->storageInstance( i );
			
		if ( WidgetCast<NounShip>( pInstance ) )
		{
			NounShip * pShip = (NounShip *)pInstance.pointer();
			Noun * pLocation = storage.pLocation;

			dword nShipFactionId = storage.nFactionId;

			// items are only available if the players is of the same faction!
			bool bFactionValid = nShipFactionId == pDoc->factionId();
			bool bLocationValid = pLocation == NULL || pSpawn == pLocation;
			bool bAvailable = bFactionValid && bLocationValid && bSpawnValid;

			ShipButton * pButton = slotCount < m_pSlotButtons->childCount() ? (ShipButton *)m_pSlotButtons->child(slotCount) : NULL;
			if ( pButton != NULL )
				pButton->setButton( this, pShip, pLocation, bAvailable, true );						// update existing button
			else
				new ShipButton( m_pSlotButtons, this, pShip, pLocation, bAvailable, true );			// create new button
			slotCount++;
		}
		else if ( WidgetCast<NounCargo>( pInstance ) )
		{
			NounCargo * pCargo = (NounCargo *)pInstance.pointer();
			CargoButton * pButton = cargoCount < m_pCargoButtons->childCount() ? (CargoButton *)m_pCargoButtons->child(cargoCount) : NULL;
			if ( pButton != NULL )
				pButton->setButton( this, pCargo, true );
			else
				new CargoButton( m_pCargoButtons, this, pCargo, true );
			cargoCount++;
		}
	}
	
	// remove extra buttons
	for(i=slotCount;i<m_pSlotButtons->childCount();i++)
		((ShipButton *)m_pSlotButtons->child(i))->destroy();
	for(i=cargoCount;i<m_pCargoButtons->childCount();i++)
		((CargoButton *)m_pCargoButtons->child(i))->destroy();

	// relayout the buttons
	((WindowLayout *)m_pShipButtons)->doLayout();
	// invalidate the message queue
	context()->setMessageQueueInvalid();

	CharString cargo;
	cargo.format( "SELECT CARGO: %d Items, %d Selected", cargoCount, m_SelectedCargo.size() );
	if ( m_pSelectedShip.valid() )
		cargo += CharString().format(", %d Available", m_pSelectedShip->availableCargo() );
	m_pTextCargo->setText( cargo );

	return shipCount == 1 && slotCount == 0 && cargoCount == 0;
}

//----------------------------------------------------------------------------

ViewSelectShip::ShipButton::ShipButton( NodeWindow * pParent, ViewSelectShip * pView, NounShip * pShip, Noun * pSpawn, bool bAvailable, bool bCanBuild  ) 
{
	m_Flags |= NOCLIP;
	m_Style = HAS_BACK | EFFECT_HIGHLIGHT | EFFECT_FADEIN | SMOOTH_LL;
	m_Alpha = 0.35f;

	setContext( pParent->context() );
	pParent->attachNode( this );

	setWindowStyle( new WindowStyle( *windowStyle() ) );
	setWindow( RectInt( PointInt(0,0), SizeInt( 120, 18 ) ) );
	setButton( pView, pShip, pSpawn, bAvailable, bCanBuild );
}

void ViewSelectShip::ShipButton::onRender( RenderContext & context, const RectInt & window )
{
	WindowButton::onRender( context, window );

	if ( m_pView->m_pSelectedShip == m_pShip && fmod( m_ActiveTime, 1.0f ) < 0.6f )	
		renderGlow( context );
}

void ViewSelectShip::ShipButton::onButtonUp()
{
	WindowButton::onButtonUp();
	m_pView->onSelectShip( m_pShip, m_pSpawn );
}

Color ViewSelectShip::ShipButton::labelColor() const
{
	return m_bAvailable ? (m_bCanBuild ? WHITE : GREY) : RED;
}

void ViewSelectShip::ShipButton::setButton( ViewSelectShip * pView, NounShip * pShip, Noun * pSpawn, bool bAvailable, bool bCanBuild )
{
	m_pView = pView;
	m_pShip = pShip;
	m_pSpawn = pSpawn;
	m_bAvailable = bAvailable;
	m_bCanBuild = bCanBuild;

	// get the full name of the ship type
	CharString name = pShip->nounContext()->name();
	// set the tool-tip
	setTip( name );

	// set the label, keep it under 15 characters
	name.left( 15 );
	setLabel( name );
}

//----------------------------------------------------------------------------

ViewSelectShip::CargoButton::CargoButton( NodeWindow * pParent, ViewSelectShip * pView, NounCargo * pCargo, bool available ) 
{
	m_Flags |= NOCLIP;
	m_Style = HAS_BACK | EFFECT_HIGHLIGHT | EFFECT_FADEIN | SMOOTH_LL;
	m_Alpha = 0.75f;

	setContext( pParent->context() );
	pParent->attachNode( this );

	setWindowStyle( new WindowStyle( *windowStyle() ) );
	setWindow( RectInt( PointInt(0,0), SizeInt( 32, 24 ) ) );
	setButton( pView, pCargo, available );
}

void ViewSelectShip::CargoButton::onUpdate( float t )
{
	WindowButton::onUpdate( t );
}

void ViewSelectShip::CargoButton::onRender( RenderContext & context, const RectInt & window )
{
	WindowButton::onRender( context, window );

	if ( m_pView->m_SelectedCargo.search( m_pCargo.pointer() ) >= 0 && fmod( m_ActiveTime, 1.0f ) < 0.6f )	
		renderGlow( context );

	// draw the quantity
	Font * pFont = windowStyle()->font();
	if ( pFont != NULL )
	{
		WideString quantity;
		quantity.format( "%d", m_pCargo->quantity() );

		SizeInt stringSize( pFont->size( quantity ) );
		PointInt stringPos( window.m_Right - stringSize.width, window.top );

		Font::push( context.display(), pFont, stringPos, quantity, WHITE );
	}
}

void ViewSelectShip::CargoButton::onButtonUp()
{
	WindowButton::onButtonUp();
	m_pView->onSelectCargo( m_pCargo );
}

void ViewSelectShip::CargoButton::setButton( ViewSelectShip * pView, NounCargo * pCargo, bool available )
{
	m_pView = pView;
	m_pCargo = pCargo;

	// set the tool-tip
	setTip( pCargo->name() );
	// show availablity
	setIconColor( available ? WHITE : GREY );
	// determine the icon
	Resource * pResource = pCargo->nounContext()->resource( "ICON" );
	if ( pResource )
	{
		Material * pIcon = WidgetCast<Material>( pResource );
		if (! pIcon )
		{
			Image * pImage = WidgetCast<Image>( pResource );
			if ( pImage )
				pIcon = new Material( pImage, PrimitiveMaterial::ADDITIVE );
		}

		setIcon( pIcon );
	}
}

//----------------------------------------------------------------------------
// EOF

